
#ifndef __SQLITE_TRANSACTOR_H
#define __SQLITE_TRANSACTOR_H

#include <map>
#include <vector>
#include <string>
#include <stdint.h>
#include "SQLiteDB.h"
#include "../utils/StringUtils.h"

/** A deferred evaluation data structure class that wraps and manages
 * SQL queries regarding a database record. */
class SQLiteTransactor
{
   public:
      SQLiteTransactor(SQLiteDB *isqldb, const char *itablename, const char *ipkeyfield, const char *ipkeyvalue);
      virtual ~SQLiteTransactor();
      const char *getField(const char *fname);
      const char *getTableName();
      void setField(const char *fname, const char *fvalue);
      void setField(const char *fname, const char fvalue);
      void setField(const char *fname, const int64_t fvalue);
      void setField(const char *fname, const int fvalue);
      void setFieldAsTime(const char *fname, const apr_time_t fvalue);
      virtual void update();
      virtual void commit();
      void precache();
      void doNotSave();
   protected:
      void do_update();
      void do_commit();
   private:
      void loadTableDefinition(const char *itablename, std::vector<char *> &fieldnames, std::vector<char *> &fieldtypes);
      void parseTableDefinition(char *table_definition, std::vector<char *> &fieldnames, std::vector<char *> &fieldtypes);
      /** Properties map key/value pairs. These are of fields in the
       * database record, defining a value and dirty flag in terms of
       * a key. */
      std::map<std::string, std::pair<std::string, int> > propmap;
      /** The database pointer. */
      SQLiteDB *sqldb;
      /** The name of the table that this record is in. */
      char *tablename;
      /** The field name of the primary key of this table. */
      char *pkeyfield;
      /** The value name of the primary key of this table. */
      char *pkeyvalue;
      /** A flag to suggest that not saving the contained data is
       * okay. */
      bool uncommitted_data_not_an_error;
};

#define SETGET_MACRO_AS_STRING_DECL(tclass, functionname, sqlite_transactor, field_name) \
   void set##functionname(const char *iobject); \
   const char *get##functionname();
#define SETGET_MACRO_AS_STRING(tclass, functionname, sqlite_transactor, field_name) \
   void tclass::set##functionname(const char *iobject) \
   { \
      if (sqlite_transactor) \
      { \
         sqlite_transactor->setField(field_name, iobject); \
      } else \
      { \
         errprint("cannot set field '%s' to string, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   const char *tclass::get##functionname() \
   { \
      if (sqlite_transactor) \
      { \
         return sqlite_transactor->getField(field_name); \
      } else \
      { \
         errprint("cannot get field '%s' as string, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   }

#define SETGET_MACRO_AS_APRDATE_DECL(tclass, functionname, sqlite_transactor, field_name) \
   void set##functionname(const apr_time_t iobject); \
   void set##functionname##ToNow(); \
   apr_time_t get##functionname##AsDate();
#define SETGET_MACRO_AS_APRDATE(tclass, functionname, sqlite_transactor, field_name) \
   void tclass::set##functionname(const apr_time_t iobject) \
   { \
      if (sqlite_transactor) \
      { \
         sqlite_transactor->setFieldAsTime(field_name, iobject); \
      } else \
      { \
         errprint("cannot set field '%s' to date, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   void tclass::set##functionname##ToNow() \
   { \
      set##functionname(apr_time_now()); \
   } \
\
   apr_time_t tclass::get##functionname##AsDate() \
   { \
      if (sqlite_transactor) \
      { \
         return apr_date_parse_rfc(sqlite_transactor->getField(field_name)); \
      } else \
      { \
         errprint("cannot get field '%s' as date, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
      return apr_time_t(0); \
   }

#define SETGET_MACRO_AS_CHAR_DECL(tclass, functionname, sqlite_transactor, field_name) \
   void set##functionname(const char iobject); \
   char get##functionname##AsChar();
#define SETGET_MACRO_AS_CHAR(tclass, functionname, sqlite_transactor, field_name) \
   void tclass::set##functionname(const char iobject) \
   { \
      if (sqlite_transactor) \
      { \
         sqlite_transactor->setField(field_name, iobject); \
      } else \
      { \
         errprint("cannot set field '%s' to char, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   char tclass::get##functionname##AsChar() \
   { \
      if (sqlite_transactor) \
      { \
         return (sqlite_transactor->getField(field_name))[0]; \
      } else \
      { \
         errprint("cannot get field '%s' as char, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
      return '\0'; \
   }

#define SETGET_MACRO_AS_INT64_DECL(tclass, functionname, sqlite_transactor, field_name) \
   void set##functionname(const int64_t iobject); \
   int64_t get##functionname##AsInt64();
#define SETGET_MACRO_AS_INT64(tclass, functionname, sqlite_transactor, field_name) \
   void tclass::set##functionname(const int64_t iobject) \
   { \
      if (sqlite_transactor) \
      { \
         char *tempstring = StringUtils::varprintf("%" APR_INT64_T_FMT, iobject); \
         sqlite_transactor->setField(field_name, tempstring); \
         free(tempstring); \
      } else \
      { \
         errprint("cannot set field '%s' to int64_t, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   int64_t tclass::get##functionname##AsInt64() \
   { \
      if (sqlite_transactor) \
      { \
         int64_t output; \
         const char *repstring = sqlite_transactor->getField(field_name); \
         sscanf(repstring, "%" APR_INT64_T_FMT, &output); \
         return output; \
      } else \
      { \
         errprint("cannot get field '%s' as int64_t, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
      return 0; \
   }

#define SETGET_MACRO_AS_INT_DECL(tclass, functionname, sqlite_transactor, field_name) \
   void set##functionname(const int iobject); \
   int get##functionname##AsInt();
#define SETGET_MACRO_AS_INT(tclass, functionname, sqlite_transactor, field_name) \
   void tclass::set##functionname(const int iobject) \
   { \
      if (sqlite_transactor) \
      { \
         char *tempstring = StringUtils::varprintf("%d", iobject); \
         sqlite_transactor->setField(field_name, tempstring); \
         free(tempstring); \
      } else \
      { \
         errprint("cannot set field '%s' to int, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   int tclass::get##functionname##AsInt() \
   { \
      if (sqlite_transactor) \
      { \
         int output = -1; \
         const char *repstring = sqlite_transactor->getField(field_name); \
         sscanf(repstring, "%d", &output); \
         return output; \
      } else \
      { \
         errprint("cannot get field '%s' as int, transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
      return 0; \
   }

#define INCDEC_MACRO_AS_INT64_DECL(tclass, functionname, sqlite_transactor, field_name) \
   void increment##functionname(); \
   void decrement##functionname(); \
   void addTo##functionname(const int64_t iadd); \
   void subtractFrom##functionname(const int64_t isubtract);
#define INCDEC_MACRO_AS_INT64(tclass, functionname, sqlite_transactor, field_name) \
   void tclass::increment##functionname() \
   { \
      if (sqlite_transactor) \
      { \
         int64_t work; \
         const char *repstring = sqlite_transactor->getField(field_name); \
         sscanf(repstring, "%" APR_INT64_T_FMT, &work); \
         char *tempstring = StringUtils::varprintf("%" APR_INT64_T_FMT, ++work); \
         sqlite_transactor->setField(field_name, tempstring); \
         free(tempstring); \
      } else \
      { \
         errprint("cannot increment field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   void tclass::decrement##functionname() \
   { \
      if (sqlite_transactor) \
      { \
         int64_t work; \
         const char *repstring = sqlite_transactor->getField(field_name); \
         sscanf(repstring, "%" APR_INT64_T_FMT, &work); \
         char *tempstring = StringUtils::varprintf("%" APR_INT64_T_FMT, --work); \
         sqlite_transactor->setField(field_name, tempstring); \
         free(tempstring); \
      } else \
      { \
         errprint("cannot increment field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   void tclass::addTo##functionname(const int64_t iadd) \
   { \
      if (sqlite_transactor) \
      { \
         int64_t work; \
         const char *repstring = sqlite_transactor->getField(field_name); \
         sscanf(repstring, "%" APR_INT64_T_FMT, &work); \
         char *tempstring = StringUtils::varprintf("%" APR_INT64_T_FMT, work + iadd); \
         sqlite_transactor->setField(field_name, tempstring); \
         free(tempstring); \
      } else \
      { \
         errprint("cannot add to field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   void tclass::subtractFrom##functionname(const int64_t isubtract) \
   { \
      if (sqlite_transactor) \
      { \
         int64_t work; \
         const char *repstring = sqlite_transactor->getField(field_name); \
         sscanf(repstring, "%" APR_INT64_T_FMT, &work); \
         char *tempstring = StringUtils::varprintf("%" APR_INT64_T_FMT, work - isubtract); \
         sqlite_transactor->setField(field_name, tempstring); \
         free(tempstring); \
      } else \
      { \
         errprint("cannot subtract from field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   }   

#define ADDRMV_MACRO_AS_CHARFLAGS_DECL(tclass, functionname, sqlite_transactor, field_name) \
   int check##functionname(const char imark); \
   void add##functionname(const char imark, int maxallowed); \
   void remove##functionname(const char imark); \
   void removeall##functionname(const char imark); \
   void remove##functionname(const char imark, int numbertoremove);
#define ADDRMV_MACRO_AS_CHARFLAGS(tclass, functionname, sqlite_transactor, field_name) \
   int tclass::check##functionname(const char imark) \
   { \
      if (sqlite_transactor) \
      { \
         const char *cfield = sqlite_transactor->getField(field_name); \
         int count = 0; \
         if (cfield) \
            for (int i = 0; i < strlen(cfield); i++) \
               count += (cfield[i] == imark); \
         return count; \
      } else \
      { \
         errprint("cannot check count for field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   void tclass::add##functionname(const char imark, int maxallowed) \
   { \
      if (sqlite_transactor) \
      { \
         int count = check##functionname(imark); \
         if ((count + 1) > maxallowed) return; \
         const char *cfield = sqlite_transactor->getField(field_name); \
         int slen = (cfield) ? strlen(cfield) : 0; \
         char *nfield = (char *)malloc(sizeof(char)*(slen + 2)); \
         if (cfield) strcpy(nfield, cfield); \
         nfield[slen+0] = imark; \
         nfield[slen+1] = '\0'; \
         sqlite_transactor->setField(field_name, nfield); \
         free(nfield); \
      } else \
      { \
         errprint("cannot add to field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   } \
\
   void tclass::remove##functionname(const char imark) \
   { \
      remove##functionname(imark, 1); \
   } \
\
   void tclass::removeall##functionname(const char imark) \
   { \
      remove##functionname(imark, check##functionname(imark)); \
   } \
\
   void tclass::remove##functionname(const char imark, int numbertoremove) \
   { \
      if (sqlite_transactor) \
      { \
         int toremove = numbertoremove; \
         const char *cfield = sqlite_transactor->getField(field_name); \
         int slen = (cfield) ? strlen(cfield) : 0; \
         if (slen == 0) return; \
         char *nfield = strdup(cfield); \
         int j = 0; \
         for (int i = 0; i < slen; i++) \
         { \
            if ((cfield[i] == imark) && (toremove)) \
            { \
               toremove--; \
            } else \
            { \
               nfield[j] = cfield[i]; \
               j++; \
            } \
         } \
         sqlite_transactor->setField(field_name, nfield); \
         free(nfield); \
      } else \
      { \
         errprint("cannot remove from field '%s', transactor %s not ready (is NULL)", field_name, #sqlite_transactor); \
      } \
   }

#endif
